﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Borg
{
    public sealed class Logger : RuntimeElement, IDisposable
    {
        private readonly object _monitor = new object();
        private readonly IList<ILogger> _loggers = new List<ILogger>();

        public const string IsBorgLoggedName = "IsBorgLogged";

        internal Logger(Runtime runtime, LoggerConfig[] loggerConfigs)
            : base(runtime)
        {
            Runtime.ShutdownFinished += (EventHandler)((sender, args) => Dispose());
            
            if (loggerConfigs == null)
                return;

            foreach (var loggerConfig in loggerConfigs)
            {
                ILogger logger;

                if (!string.IsNullOrEmpty(loggerConfig.Path))
                    logger =
                        Activator.Create<ILogger>(
                            loggerConfig.Type,
                            new object[] {PathHelper.GetFullPath(loggerConfig.Path)});
                else
                    logger = Activator.Create<ILogger>(loggerConfig.Type, new object[0]);
                
                _loggers.Add(logger);
            }
        }

        internal void Init()
        {
            foreach (var logger in _loggers)
                logger.Init(Runtime);
        }

        public void Dispose()
        {
            foreach (var disposable in _loggers)
                disposable.Dispose();
            
            _loggers.Clear();
        }

        public void Add(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            InternalAdd(logger);
        }

        public void Remove(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            if (!CheckAccess())
                throw GetAccessException();
            
            InternalRemove(logger);
        }

        private void InternalAdd(ILogger logger)
        {
            logger.Init(Runtime);

            lock (_monitor)
                _loggers.Add(logger);
        }

        private void InternalRemove(ILogger logger)
        {
            lock (_monitor)
                _loggers.Remove(logger);

            logger.Dispose();
        }

        private void Log<T>(VerbosityLevel level,
            IEnumerable<LogHandler<T>> handlers, T args, bool logError = true)
            where T : EventArgs
        {
            if (level > Runtime.VerbosityLevel)
                return;

            lock (_monitor)
            {
                foreach (var handler in handlers)
                {
                    try
                    {
                        handler(args);
                    }
                    catch (Exception ex)
                    {
                        if (logError)
                            Log(
                                level,
                                _loggers.Select(
                                    logger =>
                                    new LogHandler<ExceptionEventArgs>(
                                        logger.LogUnhandledException)),
                                new ExceptionEventArgs(ex), false);
                    }
                }
            }
        }

        internal void LogContractAdded(ContractEventArgs args)
        {
            Log(
                VerbosityLevel.Normal,
                _loggers.Select(
                    logger =>
                    new LogHandler<ContractEventArgs>(logger.LogContractAdded)), args);
        }

        internal void LogContractRemoved(ContractEventArgs args)
        {
            Log(
                VerbosityLevel.Normal,
                _loggers.Select(
                    logger =>
                    new LogHandler<ContractEventArgs>(logger.LogContractRemoved)), args);
        }

        internal void LogPluginAdded(PluginEventArgs args)
        {
            Log(
                VerbosityLevel.Normal,
                _loggers.Select(
                    logger =>
                    new LogHandler<PluginEventArgs>(logger.LogPluginAdded)), args);
        }

        internal void LogPluginRemoved(PluginEventArgs args)
        {
            Log(
                VerbosityLevel.Normal,
                _loggers.Select(
                    logger =>
                    new LogHandler<PluginEventArgs>(logger.LogPluginRemoved)), args);
        }

        internal void LogUnhandledException(ExceptionEventArgs args)
        {
            if (args.Exception.Data.Contains("IsBorgLogged") &&
                (bool)args.Exception.Data["IsBorgLogged"])
                return;

            args.Exception.Data.Add("IsBorgLogged", true);

            Log(
                VerbosityLevel.Minimal,
                _loggers.Select(
                    logger =>
                    new LogHandler<ExceptionEventArgs>(logger.LogUnhandledException)), args);
        }

        internal void LogCreated(ExtensionEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<ExtensionEventArgs>(logger.LogCreated)), args);
        }

        internal void LogActivated(ExtensionEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<ExtensionEventArgs>(logger.LogActivated)), args);
        }

        internal void LogDeactivated(ExtensionEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<ExtensionEventArgs>(logger.LogDeactivated)), args);
        }

        internal void LogDestroyed(ExtensionEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<ExtensionEventArgs>(logger.LogDestroyed)), args);
        }

        internal void LogOpened(SlotEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<SlotEventArgs>(logger.LogOpened)), args);
        }

        internal void LogClosed(SlotEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<SlotEventArgs>(logger.LogClosed)), args);
        }

        internal void LogPlugged(CompositionEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<CompositionEventArgs>(logger.LogPlugged)), args);
        }

        internal void LogUnplugged(CompositionEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<CompositionEventArgs>(logger.LogUnplugged)), args);
        }

        internal void LogTagged(TagEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<TagEventArgs>(logger.LogTagged)), args);
        }

        internal void LogUntagged(TagEventArgs args)
        {
            Log(
                VerbosityLevel.Detailed,
                _loggers.Select(
                    logger =>
                    new LogHandler<TagEventArgs>(logger.LogUntagged)), args);
        }

        internal void Log(LogArgs args)
        {
            Log(VerbosityLevel.Normal, args);
        }

        public void Log(VerbosityLevel level, LogType type, object source, string message, params object[] args)
        {
            Log(level, new LogArgs(type, source, message, args));
        }

        public void Log(VerbosityLevel level, LogArgs args)
        {
            Log(level, _loggers.Select(
                logger => new LogHandler<LogArgs>(logger.Log)), args);
        }

        public void Log(VerbosityLevel level, CustomLogArgs args)
        {
            Log(level, _loggers.Select(
                logger => new LogHandler<CustomLogArgs>(logger.Log)), args);
        }

        private delegate void LogHandler<in T>(T args) where T : class;
    }
}
